package store

import (
	"context"
	"go-caipu/pkg/infra/log"
	"go-caipu/pkg/plugins"
	"go-caipu/pkg/plugins/manager/loader"
	"go-caipu/pkg/plugins/manager/registry"
	"go-caipu/pkg/plugins/manager/sources"
	"sort"
	"sync"
	"time"
)

/* 后续迁移位置：service/pluginsintegration/pluginstore/ */
type Service struct {
	pluginRegistry registry.Service
	pluginLoader   loader.Service
}

func ProvideService(pluginRegistry registry.Service, pluginSource sources.Registry, pluginLoader loader.Service) (*Service, error) {
	ctx := context.Background()
	start := time.Now()
	totalPlugins := 0
	logger := log.New("plugin.store")
	logger.Info("Loading plugins...")

	for _, ps := range pluginSource.List(ctx) {
		loadedPlugins, err := pluginLoader.Load(ctx, ps)
		if err != nil {
			logger.Error("Loading plugin source failed", "source", ps.PluginClass(ctx), "error", err)
			return nil, err
		}
		totalPlugins += len(loadedPlugins)
	}
	logger.Info("Plugins loaded", "count", totalPlugins, "duration", time.Since(start))

	return New(pluginRegistry, pluginLoader), nil
}
func (s *Service) Run(ctx context.Context) error {
	<-ctx.Done()
	s.shutDown(ctx)
	return ctx.Err()
}
func New(pluginRegistry registry.Service, pluginLoader loader.Service) *Service {
	return &Service{
		pluginRegistry: pluginRegistry,
		pluginLoader:   pluginLoader,
	}
}

func (s *Service) Plugin(ctx context.Context, pluginID string) (plugins.PluginDTO, bool) {
	p, exists := s.plugin(ctx, pluginID)
	if !exists {
		return plugins.PluginDTO{}, false
	}

	return p.ToDTO(), true
}
func (s *Service) Plugins(ctx context.Context, pluginTypes ...plugins.Type) []plugins.PluginDTO {
	// if no types passed, assume all
	if len(pluginTypes) == 0 {
		pluginTypes = plugins.PluginTypes
	}

	var requestedTypes = make(map[plugins.Type]struct{})
	for _, pt := range pluginTypes {
		requestedTypes[pt] = struct{}{}
	}

	pluginsList := make([]plugins.PluginDTO, 0)
	for _, p := range s.availablePlugins(ctx) {
		if _, exists := requestedTypes[p.Type]; exists {
			pluginsList = append(pluginsList, p.ToDTO())
		}
	}
	return pluginsList
}
func (s *Service) plugin(ctx context.Context, pluginID string) (*plugins.Plugin, bool) {
	p, exists := s.pluginRegistry.Plugin(ctx, pluginID)

	if !exists {
		return nil, false
	}

	if p.IsDecommissioned() {
		return nil, false
	}
	return p, true
}

// availablePlugins returns all non-decommissioned plugins from the registry sorted by alphabetic order on `plugin.ID`
func (s *Service) availablePlugins(ctx context.Context) []*plugins.Plugin {
	var res []*plugins.Plugin
	for _, p := range s.pluginRegistry.Plugins(ctx) {
		if !p.IsDecommissioned() {
			res = append(res, p)
		}
	}
	sort.SliceStable(res, func(i, j int) bool {
		return res[i].ID < res[j].ID
	})
	return res
}

func (s *Service) shutDown(ctx context.Context) {
	var wg sync.WaitGroup
	for _, plugin := range s.pluginRegistry.Plugins(ctx) {
		wg.Add(1)
		go func(ctx context.Context, p *plugins.Plugin) {
			defer wg.Done()
			p.Logger().Debug("Stopping plugin", "plugin", p.Name)
			if err := s.pluginLoader.Unload(ctx, p.ID); err != nil {
				p.Logger().Error("Failed to stop plugin", "error", err)
			}
			p.Logger().Debug("Plugin stopped")
		}(ctx, plugin)
	}
	wg.Wait()
}
